import { VRC6Channels, VRC6Mixer } from "./VRC6Mixer";

const LookupTable = [
	[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
	[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];

export class VRC6Pulse {

	private enable: boolean = false;
	private volume: number = 0;
	private dutyIndex: number = 0;
	private dutyType: number = 0;
	private timer: number = 0;
	private timerMax: number = 0;
	private outValue: number = 0;
	private channel: VRC6Channels;
	private readonly mixer: VRC6Mixer;

	constructor(mixer: VRC6Mixer, channel: VRC6Channels) {
		this.mixer = mixer;
		this.channel = channel;
	}

	Reset() {
		this.enable = false;
		this.timer = 0;
		this.timerMax = 0;
		this.volume = 0;
		this.dutyType = 0;
		this.dutyIndex = 0;
	}

	WriteIO(address: number, value: number) {
		address &= 3;
		switch (address) {
			case 0:
				this.SetDutyAndVolume(value);
				break;
			case 1:
				this.SetTimerLow(value);
				break;
			case 2:
				this.SetTimerHighAndEnabled(value);
				break;
		}
	}

	ClockRate() {
		if (!this.enable)
			return;

		if (this.timer === 0) {
			this.timer = this.timerMax;
			this.Step();
		} else {
			this.timer--;
		}
	}

	/**设定Duty类型、音量或包络Flag以及其值，相当于0x4000 */
	private SetDutyAndVolume(value: number) {
		this.dutyType = (value >> 4) & 7;
		if ((value & 0x80) !== 0)
			this.dutyType = 8;

		this.volume = value & 0xF;
	}

	/**设定频率低位，相当于0x4002 */
	private SetTimerLow(value: number) {
		this.timerMax = (this.timerMax & 0xF00) | value;
	}

	/**设定频率高位，相当于0x4003 */
	private SetTimerHighAndEnabled(value: number) {
		this.timerMax = (this.timerMax & 0xFF) | ((value & 0xF) << 8);
		this.enable = (value & 0x80) !== 0;
	}

	private Step() {
		if (LookupTable[this.dutyType][this.dutyIndex] === 0)
			this.outValue = 0;
		else
			this.outValue = this.volume;

		this.dutyIndex++;
		this.dutyIndex &= 0xF;
		this.mixer.UpdateAmp(this.outValue, this.channel);
	}
}